/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package provider.impl.coalescent.data;

import coalescent.data.K69Data;
import coalescent.model.K69;
import coalescent.provider.Providers;

import com.google.common.annotations.VisibleForTesting;

import commons.core.Simulator;
import commons.provider.ProviderException;
import commons.provider.impl.BaseProviderImpl;
import commons.util.FreqDist;

import org.openide.modules.InstalledFileLocator;
import org.openide.util.Utilities;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @since
 * @version
 * @author Susanta Tewari
 * @history Created on 3/20/13.
 */
public class K69DataSimulatorProviderImpl_MS extends BaseProviderImpl<Simulator.DATA<K69, K69Data>>
        implements Providers.K69DataSimulatorProvider {

    private File exeFile = null;

    @Override
    public Class<K69Data> getDataClass() {
        return K69Data.class;
    }

    @Override
    public Class<K69> getModelClass() {
        return K69.class;
    }

    private String getDefaultBinaryPath() {

        String default_binary_path = null;
        final String windows_path  = "modules/bin/ms/ms.exe";
        final String unix_path     = "modules/bin/ms/ms";

        if (Utilities.isWindows()) default_binary_path = windows_path;
        else default_binary_path = unix_path;

        return default_binary_path;
    }

    private File getExeFile() throws ProviderException {

        if (exeFile == null) {

            final String defaultBinaryPath = getDefaultBinaryPath();
            File msExe                     = null;

            try {
                msExe = new File(getClass().getResource("/" + defaultBinaryPath).toURI());
            } catch (URISyntaxException e) {
                throw new ProviderException(e);
            }

            exeFile = ((msExe != null) && msExe.exists())
                      ? msExe
                      : InstalledFileLocator.getDefault().locate(defaultBinaryPath,
                          "coalescent.provider.impl", false);
        }

        if (exeFile == null) throw new ProviderException("problem with ms binary");

        final boolean executable = exeFile.setExecutable(true);

        if (!executable) throw new ProviderException("Can`t set executable permission: " + exeFile);

        return exeFile;
    }

    @VisibleForTesting
    void setExeFile(final File file) {
        this.exeFile = file;
    }

    String[] getPBArgs(final int sampleSize, final int simCount, final double theta,
                       final File exe_file) {

        final String cmd = exe_file.getAbsolutePath() + " " + sampleSize + " " + simCount + " -t "
                           + theta;

        if (Utilities.isWindows()) {
            return new String[] { "cmd.exe", "/C", cmd };
        } else {
            return new String[] { "sh", "-c", cmd };
        }
    }

    @Override
    public Simulator.DATA<K69, K69Data> create() throws ProviderException {

        final File exe_file = getExeFile();

        return new Simulator.DATA<K69, K69Data>() {

            @Override
            public Iterable<K69Data> simulate(final DataInput<K69> input, final int simCount) {

                final double theta            = input.getModel().getMutationRate();
                final int sampleSize          = input.getSampleSize();
                final String[] pbArgs         = getPBArgs(sampleSize, simCount, theta, exe_file);
                final List<K69Data> data_sets = new ArrayList<>(simCount);

                try {

                    final ProcessBuilder pb = new ProcessBuilder(pbArgs);
                    final Process process   = pb.start();
                    final BufferedReader in =
                        new BufferedReader(new InputStreamReader(process.getInputStream()));
                    String line;
                    final List<String> lines      = new ArrayList<>();
                    int trigger                   = 0;
                    int simCounter                = 0;
                    final String siteCount_prefix = "segsites: ";

                    while ((line = in.readLine()) != null) {

                        if (line.startsWith("//")) trigger = 1;
                        if (trigger == 0) continue;

                        if (trigger == 2) {

                            int siteCount = Integer.parseInt(line.split(siteCount_prefix)[1]);

                            if (siteCount == 0) {

                                trigger = 0;

                                lines.clear();
                                data_sets.add(null);

                                continue;
                            }
                        }

                        if (trigger > 3) {

                            lines.add(line);

                            if (lines.size() == sampleSize) {

                                simCounter++;

                                data_sets.add(createData(theta, lines));
                                lines.clear();

                                if (simCounter == simCount) break;

                                trigger = 0;

                                continue;
                            }
                        }

                        if (trigger > 0) trigger++;
                    }

                } catch (IOException ex) {
                    throw new RuntimeException("", ex);
                }

                return data_sets;
            }
        };
    }

    private K69Data createData(final double theta, final List<String> lines) {

        final Map<String, Integer> freq_map = new FreqDist<>(lines).getFrequencyDist();
        final String[] alleleSeq            = freq_map.keySet().toArray(new String[0]);
        final String[] alleleFreq           = new String[alleleSeq.length];

        for (int i = 0; i < alleleSeq.length; i++) {
            alleleFreq[i] = freq_map.get(alleleSeq[i]).toString();
        }

        return new K69Data(theta, alleleSeq, alleleFreq);
    }
}
